# -*- coding: utf-8 -*-
"""
    moe.wiki.models
    ~~~~~~~~~~~~~~~

    Wiki models.

    :copyright: 2009 by tipfy.org.
    :license: BSD, see LICENSE.txt for more details.
"""
import datetime
import logging

from google.appengine.api import datastore_errors
from google.appengine.api import taskqueue
from google.appengine.ext import db

from werkzeug import cached_property

from tipfy.appengine.db import KeyProperty, get_entity_dict, populate_entity

from moe.models import DateMixin, ArticleRevisionModel
from moe.models import User

SCHEMA_VERSION = '2'


class WikiRevision(ArticleRevisionModel):
    """Stores content for wiki pages. The latest version is always a root
    entity and past versions have the latest version as ancestor.
    """
    # Generated HTML with table of contents: lists all headings in the page.
    toc = db.TextProperty()
    schema = db.StringProperty(default=SCHEMA_VERSION)

    @cached_property
    def version(self):
        """Returns the current version id. Latest versions always have a key
        name '__latest__', while older versions are children of it and have a
        key id.
        """
        if not self.is_saved():
            return None

        id = self.key().id()
        if id is not None:
            return str(id)

        return 'latest'

    @classmethod
    def get_key(cls, path, version=None):
        parts = [
            WikiPage.kind(), path,
            cls.kind(), '__latest__',
        ]
        if version is not None:
            # This is not the latest revision.
            parts += [cls.kind(), version]

        return db.Key.from_path(*parts)

    @classmethod
    def get_by_path(cls, path, version=None):
        return cls.get(cls.get_key(path, version))

    @classmethod
    def get_latest_revisions(cls, path, limit=2):
        # Latest 2 revisions.
        res = cls.all().ancestor(cls.get_key(path)) \
                       .order('-updated') \
                       .fetch(limit)

        return res or []


class WikiPage(db.Model, DateMixin):
    # Creator of this page.
    author_key = KeyProperty()
    # The URL path to the page.
    path = db.StringProperty()
    # Direct parent path.
    parent_path = db.StringProperty()
    # Paths to all parents of this page.
    parent_paths = db.StringListProperty()
    # Tags.
    tags = db.StringListProperty()

    @cached_property
    def author(self):
        # XXX use global cache
        if self.author_key:
            return User.get(self.author_key)

    @cached_property
    def latest_revision(self):
        # Latest revision entity.
        return WikiRevision.get_by_path(self.path)

    @cached_property
    def latest_revision_key(self):
        return WikiRevision.get_key(self.path)

    @classmethod
    def get_key(cls, path):
        return db.Key.from_path(cls.kind(), path)

    @classmethod
    def get_by_path(cls, path):
        return cls.get(cls.get_key(path))

    @classmethod
    def get_revision(cls, path, version=None):
        """Returns a revision for a given page and version."""
        return WikiRevision.get_by_path(path, version)

    @classmethod
    def insert_or_update(cls, path, page_values, revision_values):
        """Saves a page and revision transactionally, possibly also saving
        previous revision in the version history.
        """
        now = datetime.datetime.now()
        revision_values.update({
            'created': now,
            'updated': now,
            'is_root': True,
        })

        # Create a new revision with page as parent.
        new_revision = WikiRevision(key_name='__latest__',
            parent=WikiPage.get_key(path), **revision_values)

        def txn():
            page = WikiPage.get_by_key_name(path)
            if page is None:
                page = WikiPage(key_name=path, **page_values)
            else:
                # Page is not new, so we just want to update tags.
                page.tags = page_values.get('tags', [])

            # Set the entities to be saved.
            entities = [page, new_revision]

            # Check if we need to save old revision in the revision history.
            old_revision = WikiRevision.get_by_key_name('__latest__',
                parent=page)
            if old_revision is not None:
                # Save old data in the revision history with latest revision
                # as parent.
                old_data = get_entity_dict(old_revision)
                old_data['is_root'] = False
                entities.append(WikiRevision(parent=new_revision, **old_data))

            # XXX set a transactional task.
            # taskqueue.add(url='/path/to/my/worker', transactional=True)
            return db.put(entities)
        try:
            return db.run_in_transaction(txn)
        except datastore_errors.TransactionFailedError, e:
            logging.debug(e)
            return None


class WikiToc(db.Model):
    # Creation date.
    created = db.DateTimeProperty(auto_now_add=True)
    # Modification date.
    updated = db.DateTimeProperty(auto_now=True)
    # The URL path to the page.
    path = db.StringProperty()
    # Formatted toc contents.
    body = db.TextProperty()
    # Original toc contents.
    body_raw = db.TextProperty()


def wiki_revisions_pager(revision, cursor=None, limit=20):
    def get_query(keys_only=False, cursor=None):
        return WikiRevision.all(keys_only=keys_only, cursor=cursor) \
                           .ancestor(revision) \
                           .order('-updated')

    query = get_query(cursor=cursor)
    entities = query.fetch(limit)
    query_cursor = None

    if entities:
        query_cursor = query.cursor()

        # Check if we have "next" results.
        res = get_query(keys_only=True, cursor=query_cursor).get()
        if res is None:
            query_cursor = None

    return entities, query_cursor


def wiki_changes_pager(cursor=None, limit=20):
    def get_query(keys_only=False, cursor=None):
        return WikiPage.all(keys_only=keys_only, cursor=cursor) \
                       .order('-updated')

    query = get_query(cursor=cursor)
    entities = query.fetch(limit)

    if not entities:
        query_cursor = None
    else:
        query_cursor = query.cursor()

        # Check if we have "next" results.
        res = get_query(keys_only=True, cursor=query_cursor).get()
        if res is None:
            query_cursor = None

    return entities, query_cursor


def wiki_list_pager(parent_path=None, cursor=None, limit=20):
    def get_query(keys_only=False, cursor=None):
        query = WikiPage.all(keys_only=keys_only) \
                        .filter('parent_path', parent_path) \
                        .order('path')

        if cursor is not None:
            query.with_cursor(cursor)

        return query

    query = get_query(cursor=cursor)
    entities = query.fetch(limit)

    if not entities:
        query_cursor = None
    else:
        query_cursor = query.cursor()

        # Check if we have "next" results.
        res = get_query(keys_only=True, cursor=query_cursor).get()
        if res is None:
            query_cursor = None

    return entities, query_cursor
